#ifndef BEZIER_CURVE_H
#define BEZIER_CURVE_H

#pragma once

#include <vector>
#include <stdexcept>

#include <LDF/Utils/Point3d.h>


class BezierCurve 
{


	protected:
	
		/** The spline control points. */
		std::vector<Point3d> _controlPoints;


	public:
	
		inline BezierCurve(){ return; }

		inline ~BezierCurve(){ return; }


		/**
		 * Returns the number of control points of this curve.
		 *
		 * @return The number of control points of this curve.
		 */
		inline size_t getNumControlPoints(){ return _controlPoints.size(); }
		
		std::vector<Point3d>* getControlPoints(){ return &_controlPoints; }

		
		void printControlPoints();
		

		/**
			Using the de Castelijau algorithm, linearly interpolate the point on the Bézier curve corresponding to the parametric value @a t.
			
			@param t The relative position along the Bézier curve, where @a t is between 0.0 and 1.0 inclusive.
			@param point The resulting position of the point. Instead dynamically allocating memory for a new Spline::SplinePoint object in this method, we use the 
				Return Value Optimization (RVO) and move the method return value to parameter value.
			@exception std::invalid_argument If @a t is outside of the range 0 to 1 inclusive, an exception is thrown.
		*/
		inline void getCurvePoint(const double t, Point3d* point) throw (std::invalid_argument){
		
			if(t < 0.0 || t > 1.0){
				throw std::invalid_argument("ERROR in BezierCurve::getCurvePoint() - Parameter t must be between the range of [0.0, 1.0].");
			}

			std::vector<Point3d> deCastelijauPoint(_controlPoints);
			
			const double minusT = 1.0 - t;
			
			for(size_t r = 1; r <_controlPoints.size(); r++){
				for(size_t i = 0; i < _controlPoints.size() - r; i++){
					deCastelijauPoint[i] = (deCastelijauPoint[i] * minusT) + (deCastelijauPoint[i + 1] * t);
				}
			}
			
			(*point) = deCastelijauPoint[0];
			

			return;
		}
		
		
		/**
			Evaluate position @a t along a linear Bézier curve, using the first and the last control points from #_controlPoints. 
			This is a helper method for drawing a straight line between the first and last control points.
			
			@param t The relative position along the linear Bézier curve, where @a t is between 0.0 and 1.0 inclusive.
			@param point The resulting position of the point. Instead dynamically allocating memory for a new Spline::SplinePoint object in this method, we use the 
				Return Value Optimization (RVO) and move the method return value to parameter value.
			@exception std::invalid_argument If @a t is outside of the range 0 to 1 inclusive, an exception is thrown.
			@exception std::logic_error If #_controlPoints has less then two elements, an exception is throw.
		*/
		inline void getLinearCurvePoint(const double t, Point3d* point) throw (std::invalid_argument, std::logic_error){
		
			if(t < 0.0 || t > 1.0){
				throw std::invalid_argument("ERROR in BezierCurve::getCurvePoint() - Parameter t must be between the range of [0.0, 1.0].");
			}else if(_controlPoints.size() < 2){
				throw std::logic_error("ERROR in BezierCurve::getLinearCurvePoint() - Vector of control points has less then two elements. Linear Bezier curve cannot be evaluated.");
			}
			
			(*point) = (_controlPoints.front() * (1.0 - t)) + (_controlPoints.back() * t);
			
			return;
		}
		
		
		/**
			Adds a control point to the spline's list of control points.
		 
			@param x The x-coordinate of the control point.
			@param y The y-coordinate of the control point.
			@param z The y-coordinate of the control point.
		 */
		inline void addControlPoint(const double x, const double y, const double z = 0.0)
		{
			_controlPoints.push_back(Point3d(x, y, z));
			return;
		}		
		
		inline void modifyControlPoint(const size_t index, const double x, const double y, const double z = 0.0)
		{
			if(index >= _controlPoints.size()){
				return;
			}
			_controlPoints[index].x = x;
			_controlPoints[index].y = y;
			_controlPoints[index].z = z;
			return;
		}
		

		inline void modifyControlPoint(const size_t index, Point3d* point)
		{
			if(index >= _controlPoints.size()){
				return;
			}
			_controlPoints[index] = (*point);
			return;
		}
		
};

#endif // BEZIER_CURVE_H

